package com.xzzz.irda.guardcat.server.app;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.xzzz.common.base.exception.XzException404;
import com.xzzz.common.base.util.json.JsonUtil;
import com.xzzz.irda.guardcat.core.util.GuardcatUtil;
import com.xzzz.irda.guardcat.server.alert.AbstractAlertManager;
import com.xzzz.irda.guardcat.server.app.pojo.AppInfo;
import com.xzzz.irda.guardcat.server.app.pojo.MachineInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 应用和机器管理
 *
 * @author xzzz
 */
@Slf4j
@Service
@EnableScheduling
public class AppManager {
    private static final String APP_KEY = "guardcat:apps";
    private static final String MACHINES = "guardcat:machines:";

    @Autowired
    public AbstractAlertManager alertManager;

    @Autowired
    public StringRedisTemplate redisTemplate;

    /**
     * 添加机器
     */
    public void add(MachineInfo machine) {
        machine.setRunTime(GuardcatUtil.secondToDay(machine.getUptime()  / 1000));
        redisTemplate.opsForHash().put(APP_KEY, machine.getAppName(), JsonUtil.toJson(new AppInfo(machine.getAppName())));
        redisTemplate.opsForHash().put(MACHINES + machine.getAppName(), machine.getMachineId(), JsonUtil.toJson(machine));
    }

    /**
     * 获取全部应用名称
     * @return 应用名称
     */
    public Set<String> getAppNames() {
        Map<Object, Object> apps = redisTemplate.opsForHash().entries(APP_KEY);

        return apps.values()
            .stream()
            .map(o -> JsonUtil.toObj(o.toString(), AppInfo.class))
            .sorted(Comparator.comparingInt(AppInfo::getSort))
            .map(AppInfo::getAppName)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * 获取全部应用
     * @return 应用列表
     */
    public Set<AppInfo> getApps() {
        Map<Object, Object> apps = redisTemplate.opsForHash().entries(APP_KEY);
        return apps.values()
                .stream()
                .map(o -> JsonUtil.toObj(o.toString(), AppInfo.class))
                .sorted(Comparator.comparingInt(AppInfo::getSort))
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * 获取应用
     * @param appName 应用名称
     * @return 应用对象
     */
    public AppInfo getApp(String appName) {
        Object obj = redisTemplate.opsForHash().get(APP_KEY, appName);
        if (obj == null) {
            throw new XzException404(String.format("未查询到应用[%s]", appName));
        }
        return JsonUtil.toObj(obj.toString(),AppInfo.class);
    }

    /**
     * 获取应用的机器列表
     * @param appName 应用名称
     * @return 机器列表
     */
    public List<MachineInfo> getMachines(String appName){
        Map<Object,Object> machines = redisTemplate.opsForHash().entries(MACHINES + appName);
        if (MapUtil.isEmpty(machines)) {
            return new ArrayList<>();
        }
        return machines.values().stream()
            .map(o -> JsonUtil.toObj(o.toString(), MachineInfo.class))
            .collect(Collectors.toList());
    }

    /**
     * 获取指定机器, 性能较好
     * @param appName 应用名称
     * @param machineId 机器ID
     * @return 机器信息
     */
    public MachineInfo getMachine(String appName, String machineId) {
        for (MachineInfo machine : this.getMachines(appName)) {
            if (machine.getMachineId().equals(machineId)) {
                return machine;
            }
        }

        throw new XzException404(String.format("未找到机器 [%s]", machineId));
    }

    /**
     * 获取指定机器, 性能较差
     * @param machineId 机器ID
     * @return 机器信息
     */
    public MachineInfo getMachine(String machineId) {
        Map<Object, Object> apps = redisTemplate.opsForHash().entries(APP_KEY);

        for (Map.Entry<Object, Object> app : apps.entrySet()) {
            Map<Object, Object> machines = redisTemplate.opsForHash().entries(MACHINES + app.getKey().toString());

            for (Map.Entry<Object, Object> machine : machines.entrySet()) {
                if (machine.getKey().equals(machineId)) {
                    return JsonUtil.toObj(machine.getValue().toString(), MachineInfo.class);
                }
            }
        }
        throw new XzException404(String.format("未查询到机器:[%s]", machineId));
    }

    // ================================== 删除应用/机器 ==================================

    /**
     * 删除不在线的机器, 每4个小时删除一次
     */
    @Scheduled(cron = "0 0 0/4 * * ?")
    public void removeMachine() {
        log.warn("[GUARDCAT] 开始删除机器");
        Map<Object, Object> apps = redisTemplate.opsForHash().entries(APP_KEY);

        apps.forEach((appName, app) -> {
            List<String> deleteKey = new ArrayList<>();
            Map<Object, Object> machines = redisTemplate.opsForHash().entries(MACHINES + appName);

            machines.forEach((machineId, machineJson) -> {
                MachineInfo machineInfo = JsonUtil.toObj(machineJson.toString(), MachineInfo.class);
                if (machineInfo.isDead()) {
                    deleteKey.add(machineId.toString());
                }
            });

            if (CollUtil.isNotEmpty(deleteKey)) {
                log.info("删除机器:[{}]", deleteKey);
                redisTemplate.opsForHash().delete(MACHINES + appName, deleteKey.toArray());
            }
        });
    }

    /**
     * 删除没有机器的应用, 每4个小时删除一次
     */
    @Scheduled(cron = "0 0 0/4 * * ?")
    public void removeApps() {
        log.warn("[GUARDCAT] 开始删除应用");
        Map<Object, Object> apps = redisTemplate.opsForHash().entries(APP_KEY);
        apps.forEach((appName, app) -> {
            Map<Object, Object> machines = redisTemplate.opsForHash().entries(MACHINES + appName);
            if (MapUtil.isEmpty(machines)) {
                log.info("删除应用:[{}]", appName);
                redisTemplate.opsForHash().delete(APP_KEY, appName);
            }
        });
    }

    /**
     * 打印全部机器信息
     *
     * <code>
     *    @Scheduled(cron = "0/30 * * * * ?")
     * </code>
     */
    @Deprecated
    public void printAllMachines() {
        StringBuilder sb = new StringBuilder();
        Map<Object, Object> apps = redisTemplate.opsForHash().entries(APP_KEY);

        apps.forEach((appName, app) -> {
            sb.append(appName).append(":\n\t");

            Map<Object, Object> machines = redisTemplate.opsForHash().entries(MACHINES + appName);

            machines.forEach((machineId, machineJson) -> {
                MachineInfo machineInfo = JsonUtil.toObj(machineJson.toString(), MachineInfo.class);
                sb.append("(").append(machineInfo.getMachineStatus()).append("[").append(alertManager.countMachine(machineInfo.getMachineId(), true)).append("])")
                        .append(machineInfo.getIp()).append(":")
                        .append(machineInfo.getPort()).append("|")
                        .append(machineInfo.getMachineId())
                        .append("\n\t");
            });
            sb.append("\n");
        });

        log.info("\n=========================== 服务列表 ===========================\n" +
                "{}" +
                "===============================================================",
            sb.toString());
    }

}
